S01-17 JavaSE-泛型
[TOC]
泛型的理解和好处
传统方法的问题
需求:在 ArrayList 中添加 3 个 Dog 对象,输出 name 和 age。 传统方法实现(无泛型):
java
package com.hspedu.generic;
import java.util.ArrayList;
@SuppressWarnings({"all"})
public class Generic01 {
public static void main(String[] args) {
ArrayList arrayList = new ArrayList();
arrayList.add(new Dog("旺财", 10));
arrayList.add(new Dog("发财", 1));
arrayList.add(new Dog("小黄", 5));
// 不小心添加了 Cat 对象(编译无报错,运行时异常)
arrayList.add(new Cat("招财猫", 8));
// 遍历:需要向下转型,效率低且不安全
for (Object o : arrayList) {
Dog dog = (Dog) o;// 运行时抛出 ClassCastException
System.out.println(dog.getName() + "-" + dog.getAge());
}
}
}
class Dog {
private String name;
private int age;
public Dog(String name, int age) {
this.name = name;
this.age = age;
}
// getter/setter
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public int getAge() { return age; }
public void setAge(int age) { this.age = age; }
}
class Cat {
private String name;
private int age;
public Cat(String name, int age) {
this.name = name;
this.age = age;
}
// getter/setter
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public int getAge() { return age; }
public void setAge(int age) { this.age = age; }
}传统方法的问题分析
- 不能约束集合中元素的类型(添加 Cat 编译无报错)
- 遍历需要类型转换,数据量大时效率低
- 运行时可能抛出 ClassCastException
泛型快速体验(Generic02.java)
java
package com.hspedu.generic.improve;
import java.util.ArrayList;
@SuppressWarnings({"all"})
public class Generic02 {
public static void main(String[] args) {
// 泛型约束:只能添加 Dog 类型
ArrayList<Dog> arrayList = new ArrayList<Dog>();
arrayList.add(new Dog("旺财", 10));
arrayList.add(new Dog("发财", 1));
arrayList.add(new Dog("小黄", 5));
// arrayList.add(new Cat("招财猫", 8));// 编译报错,类型不匹配
// 遍历:无需类型转换,直接获取 Dog 类型
System.out.println("===使用泛型===");
for (Dog dog : arrayList) {
System.out.println(dog.getName() + "-" + dog.getAge());
}
}
}
class Dog {
private String name;
private int age;
public Dog(String name, int age) {
this.name = name;
this.age = age;
}
// getter/setter
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public int getAge() { return age; }
public void setAge(int age) { this.age = age; }
}泛型的好处
- 编译时检查类型,提高安全性(避免添加错误类型)
- 减少类型转换次数,提高效率:
- 无泛型:Dog → Object → Dog(添加时装箱,取出时拆箱)
- 有泛型:Dog → Dog(无需转换)
- 消除编译警告
泛型介绍
- 泛型又称参数化类型,JDK 5.0 新特性,解决数据类型安全性问题
- 类声明或实例化时指定具体类型
- 编译时无警告则运行时无 ClassCastException
- 泛型可用于类的属性、方法参数、返回值类型
泛型示例(Generic03.java)
java
package com.hspedu.generic;
@SuppressWarnings({"all"})
public class Generic03 {
public static void main(String[] args) {
// 实例化时指定泛型类型为 String
Person<String> person = new Person<String>("韩顺平教育");
person.show();// 输出:class java.lang.String
// 实例化时指定泛型类型为 Integer
Person<Integer> person2 = new Person<Integer>(100);
person2.show();// 输出:class java.lang.Integer
}
}
// 泛型类:T 为类型参数
class Person<T> {
T s;// 属性使用泛型
// 构造器使用泛型
public Person(T s) {
this.s = s;
}
// 方法返回值使用泛型
public T f() {
return s;
}
// 显示 s 的运行类型
public void show() {
System.out.println(s.getClass());
}
}泛型的语法
泛型的声明
java
// 接口泛型
interface 接口名<T, R...> {}
// 类泛型
class 类名<K, V...> {}- T、K、V 是类型占位符,任意字母均可,常用 T(Type)、K(Key)、V(Value)
- 可声明多个泛型参数(如
<T, R, M>)
泛型的实例化
java
// 完整写法
List<String> list1 = new ArrayList<String>();
// 简化写法(Java 7+ 类型推断)
List<String> list2 = new ArrayList<>();
// 迭代器泛型
Iterator<Customer> iterator = customers.iterator();泛型使用举例(GenericExercise.java)
需求:
- 创建 3 个 Student 对象
- 放入 HashSet(泛型)
- 放入 HashMap(Key:姓名,Value:Student 对象)
- 两种方式遍历
java
package com.hspedu.generic;
import java.util.*;
@SuppressWarnings({"all"})
public class GenericExercise {
public static void main(String[] args) {
// 1. HashSet 存储 Student
HashSet<Student> students = new HashSet<Student>();
students.add(new Student("jack", 18));
students.add(new Student("tom", 28));
students.add(new Student("mary", 19));
// 遍历 HashSet(增强 for)
System.out.println("===HashSet 遍历===");
for (Student student : students) {
System.out.println(student);
}
// 2. HashMap 存储(Key:String,Value:Student)
HashMap<String, Student> hm = new HashMap<String, Student>();
hm.put("milan", new Student("milan", 38));
hm.put("smith", new Student("smith", 48));
hm.put("hsp", new Student("hsp", 28));
// 遍历 HashMap 方式1:EntrySet + 迭代器
System.out.println("===HashMap 迭代器遍历===");
Set<Map.Entry<String, Student>> entries = hm.entrySet();
Iterator<Map.Entry<String, Student>> iterator = entries.iterator();
while (iterator.hasNext()) {
Map.Entry<String, Student> next = iterator.next();
System.out.println(next.getKey() + "-" + next.getValue());
}
// 遍历 HashMap 方式2:KeySet + 增强 for
System.out.println("===HashMap KeySet 遍历===");
Set<String> keySet = hm.keySet();
for (String key : keySet) {
System.out.println(key + "-" + hm.get(key));
}
}
}
class Student {
private String name;
private int age;
public Student(String name, int age) {
this.name = name;
this.age = age;
}
// getter/setter
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public int getAge() { return age; }
public void setAge(int age) { this.age = age; }
@Override
public String toString() {
return "Student{" + "name='" + name + '\'' + ", age=" + age + '}';
}
}泛型使用的注意事项和细节
- 泛型参数只能是引用类型,不能是基本类型:java
List<Integer> list = new ArrayList<>();// OK // List<int> list2 = new ArrayList<>();// 错误 - 指定泛型类型后,可传入该类型或其子类:java
Pig<A> pig = new Pig<>(new B());// B 是 A 的子类,OK - 泛型简化写法:java
ArrayList<Integer> list = new ArrayList<>();// 等价于 new ArrayList<Integer>() - 未指定泛型时,默认是 Object:java
ArrayList list = new ArrayList<>();// 等价于 ArrayList<Object> list = new ArrayList<>()
泛型课堂练习(GenericExercise02.java)
需求:
- 定义 Employee 类(name, sal, birthday),birthday 为 MyDate 类(year, month, day)
- 重写 toString,实现 getter/setter
- 创建 3 个 Employee 对象,放入 ArrayList(泛型)
- 排序:先按 name 升序,name 相同则按生日升序(定制排序)
java
package com.hspedu.generic;
import java.util.ArrayList;
import java.util.Comparator;
@SuppressWarnings({"all"})
public class GenericExercise02 {
public static void main(String[] args) {
ArrayList<Employee> employees = new ArrayList<>();
employees.add(new Employee("tom", 20000, new MyDate(1980, 12, 11)));
employees.add(new Employee("jack", 12000, new MyDate(2001, 12, 12)));
employees.add(new Employee("tom", 50000, new MyDate(1980, 12, 10)));
System.out.println("排序前:" + employees);
// 定制排序
employees.sort(new Comparator<Employee>() {
@Override
public int compare(Employee emp1, Employee emp2) {
// 验证类型
if (!(emp1 instanceof Employee && emp2 instanceof Employee)) {
System.out.println("类型不正确");
return 0;
}
// 按 name 比较
int nameCompare = emp1.getName().compareTo(emp2.getName());
if (nameCompare != 0) {
return nameCompare;
}
// name 相同,按生日比较(MyDate 需实现 compareTo)
return emp1.getBirthday().compareTo(emp2.getBirthday());
}
});
System.out.println("排序后:" + employees);
}
}
// Employee 类
class Employee {
private String name;
private double sal;
private MyDate birthday;
public Employee(String name, double sal, MyDate birthday) {
this.name = name;
this.sal = sal;
this.birthday = birthday;
}
// getter/setter
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public double getSal() { return sal; }
public void setSal(double sal) { this.sal = sal; }
public MyDate getBirthday() { return birthday; }
public void setBirthday(MyDate birthday) { this.birthday = birthday; }
@Override
public String toString() {
return "Employee{" + "name='" + name + '\'' + ", sal=" + sal + ", birthday=" + birthday + '}';
}
}
// MyDate 类(实现 Comparable 接口)
class MyDate implements Comparable<MyDate> {
private int year;
private int month;
private int day;
public MyDate(int year, int month, int day) {
this.year = year;
this.month = month;
this.day = day;
}
// getter/setter
public int getYear() { return year; }
public void setYear(int year) { this.year = year; }
public int getMonth() { return month; }
public void setMonth(int month) { this.month = month; }
public int getDay() { return day; }
public void setDay(int day) { this.day = day; }
@Override
public String toString() {
return year + "-" + month + "-" + day;
}
// 按年月日比较
@Override
public int compareTo(MyDate o) {
int yearCompare = Integer.compare(this.year, o.year);
if (yearCompare != 0) return yearCompare;
int monthCompare = Integer.compare(this.month, o.month);
if (monthCompare != 0) return monthCompare;
return Integer.compare(this.day, o.day);
}
}自定义泛型
自定义泛型类
基本语法
java
class 类名<T, R...> {
// 成员属性、方法可使用泛型
}注意细节
- 普通成员(属性、方法)可使用泛型
- 泛型数组不能初始化(无法确定类型,无法分配内存)
- 静态方法/静态属性不能使用类的泛型(静态与类相关,类加载时泛型未确定)
- 泛型类型在创建对象时确定
- 未指定泛型时,默认是 Object
应用案例(CustomGeneric_.java)
java
package com.hspedu.customgeneric;
import java.util.Arrays;
@SuppressWarnings({"all"})
public class CustomGeneric_ {
public static void main(String[] args) {
// 创建对象时指定泛型:T=Double, R=String, M=Integer
Tiger<Double, String, Integer> tiger = new Tiger<>("john");
tiger.setT(10.9);// OK
// tiger.setT("abc");// 错误,类型不匹配
System.out.println(tiger);
// 未指定泛型,默认 Object
Tiger tiger2 = new Tiger("john~~");
tiger2.setT("yy");// OK(T=Object)
System.out.println("tiger2=" + tiger2);
}
}
class Tiger<T, R, M> {
String name;
R r;// 属性使用泛型
M m;// 属性使用泛型
T t;// 属性使用泛型
T[] ts;// 泛型数组(不能初始化)
// 构造器
public Tiger(String name) {
this.name = name;
}
public Tiger(String name, R r, M m, T t) {
this.name = name;
this.r = r;
this.m = m;
this.t = t;
}
// 方法使用泛型
public R getR() { return r; }
public void setR(R r) { this.r = r; }
public M getM() { return m; }
public void setM(M m) { this.m = m; }
public T getT() { return t; }
public void setT(T t) { this.t = t; }
@Override
public String toString() {
return "Tiger{" + "name='" + name + '\'' + ", r=" + r + ", m=" + m + ", t=" + t + ", ts=" + Arrays.toString(ts) + '}';
}
}自定义泛型接口
基本语法
java
interface 接口名<T, R...> {
// 普通方法可使用泛型
R method(U u);
}注意细节
- 接口中静态成员不能使用泛型
- 泛型类型在继承接口或实现接口时确定
- 未指定泛型时,默认是 Object
应用案例(CustomInterfaceGeneric.java)
java
package com.hspedu.customgeneric;
@SuppressWarnings({"all"})
public class CustomInterfaceGeneric {
public static void main(String[] args) {
// 测试实现类
AA aa = new AA();
System.out.println(aa.get("hello"));// null
BB bb = new BB();
System.out.println(bb.get(100));// null
}
}
// 泛型接口
interface IUsb<U, R> {
int n = 10;// 静态常量(不能使用泛型)
R get(U u);
void hi(R r);
void run(R r1, R r2, U u1, U u2);
// JDK 8+ 默认方法(可使用泛型)
default R method(U u) {
return null;
}
}
// 继承接口时指定泛型类型
interface IA extends IUsb<String, Double> {}
// 实现 IA 接口(已指定泛型)
class AA implements IA {
@Override
public Double get(String s) {
return null;
}
@Override
public void hi(Double aDouble) {}
@Override
public void run(Double r1, Double r2, String u1, String u2) {}
}
// 实现接口时指定泛型类型
class BB implements IUsb<Integer, Float> {
@Override
public Float get(Integer integer) {
return null;
}
@Override
public void hi(Float aFloat) {}
@Override
public void run(Float r1, Float r2, Integer u1, Integer u2) {}
}
// 未指定泛型,默认 Object
class CC implements IUsb {
@Override
public Object get(Object o) {
return null;
}
@Override
public void hi(Object o) {}
@Override
public void run(Object r1, Object r2, Object u1, Object u2) {}
}自定义泛型方法
基本语法
java
修饰符 <T, R...> 返回类型 方法名(参数列表) {
// 方法体
}注意细节
- 泛型方法的泛型参数声明在修饰符和返回类型之间(
<T, R...>) - 泛型方法可定义在普通类或泛型类中
- 泛型方法的类型在调用时确定(编译器根据参数推断)
- 与类的泛型独立(方法自己的泛型与类泛型无关)
应用案例(CustomMethodGeneric.java)
java
package com.hspedu.customgeneric;
import java.util.ArrayList;
@SuppressWarnings({"all"})
public class CustomMethodGeneric {
public static void main(String[] args) {
Car car = new Car();
// 调用泛型方法,编译器推断 T=String, R=Integer
car.fly("宝马", 100);
// 调用泛型方法,编译器推断 T=Integer, R=Double
car.fly(300, 100.1);
// 泛型类中的泛型方法
Fish<String, ArrayList> fish = new Fish<>();
// 调用泛型方法,编译器推断 U=ArrayList, M=Float
fish.hello(new ArrayList(), 11.3f);
}
}
// 普通类中的泛型方法
class Car {
public <T, R> void fly(T t, R r) {
System.out.println("t 的类型:" + t.getClass().getSimpleName());
System.out.println("r 的类型:" + r.getClass().getSimpleName());
}
}
// 泛型类中的泛型方法
class Fish<T, R> {
// 普通方法(使用类的泛型)
public void hi(T t) {
System.out.println("t 的类型:" + t.getClass().getSimpleName());
}
// 泛型方法(自己的泛型 U, M)
public <U, M> void hello(R r, M m) {
System.out.println("r 的类型:" + r.getClass().getSimpleName());
System.out.println("m 的类型:" + m.getClass().getSimpleName());
}
}泛型的继承和通配符
核心规则
- 泛型不具备继承性:java
// List<Object> list = new ArrayList<String>();// 错误 - 通配符
<?>:支持任意泛型类型javaList<?> list = new ArrayList<String>();// OK list = new ArrayList<Integer>();// OK - 上限通配符
<? extends A>:支持 A 类及子类javaList<? extends Animal> list = new ArrayList<Dog>();// OK(Dog 是 Animal 子类) // list = new ArrayList<String>();// 错误(String 不是 Animal 子类) - 下限通配符
<? super A>:支持 A 类及父类javaList<? super Dog> list = new ArrayList<Animal>();// OK(Animal 是 Dog 父类) // list = new ArrayList<String>();// 错误(String 不是 Dog 父类)
应用场景
- 上限通配符:读取数据(如获取集合中元素,无需修改)
- 下限通配符:写入数据(如向集合中添加元素,无需读取)
- 无界通配符:既需要读取又需要写入(但限制较多))
第15章 泛型与JUnit
泛型的继承和通配符
核心代码示例
java
import java.util.ArrayList;
import java.util.List;
/**
* 泛型的继承和通配符
* @author 韩顺平
* @version 1.0
*/
public class GenericExtends {
public static void main(String[] args) {
Object o = new String("xx");
// 泛型没有继承性
List<Object> list1 = new ArrayList<>();
List<String> list2 = new ArrayList<>();
List<AA> list3 = new ArrayList<>();
List<BB> list4 = new ArrayList<>();
List<CC> list5 = new ArrayList<>();
// 1. List<?> c: 可以接受任意的泛型类型
printCollection1(list1);
printCollection1(list2);
printCollection1(list3);
printCollection1(list4);
printCollection1(list5);
// 2. List<? extends AA> c: 表示上限,可以接受AA 或者AA 子类
// printCollection2(list1);//×
// printCollection2(list2);//×
printCollection2(list3);//√
printCollection2(list4);//√
printCollection2(list5);//√
// 3. List<? super AA> c: 支持AA 类以及AA 类的父类,不限于直接父类
printCollection3(list1);//√
// printCollection3(list2);//×
printCollection3(list3);//√
// printCollection3(list4);//×
// printCollection3(list5);//×
}
// 说明: List<?> 表示任意的泛型类型都可以接受
public static void printCollection1(List<?> c) {
for (Object object : c) {
System.out.println(object);
}
}
// ? extends AA 表示上限,可以接受AA 或者AA 子类
public static void printCollection2(List<? extends AA> c) {
for (Object object : c) {
System.out.println(object);
}
}
// ? super 子类类名AA:支持AA 类以及AA 类的父类,不限于直接父类,规定了泛型的下限
public static void printCollection3(List<? super AA> c) {
for (Object object : c) {
System.out.println(object);
}
}
}
class AA {}
class BB extends AA {}
class CC extends BB {}本章作业
编程题 Homework01.java(10min)
- 定义泛型类
DAO<T>,包含Map成员变量(键为String类型,值为T类型) - 实现以下方法:
public void save(String id, T entity): 保存T类型对象到Mappublic T get(String id): 从Map中获取id对应的对象public void update(String id, T entity): 替换Map中key为id的内容public List<T> list(): 返回Map中所有T对象public void delete(String id): 删除指定id的对象
- 定义
User类:包含私有成员变量id(int)、age(int)、name(String) - 创建
DAO类对象,调用上述方法操作User对象,使用 JUnit 单元测试
JUnit
为什么需要JUnit
- 传统方式需在
main方法中编写测试代码 - 多个功能测试需来回注销,切换麻烦
- 需直接运行单个方法并获取测试信息
基本介绍
- JUnit 是 Java 语言的单元测试框架
- 多数 Java 开发环境已集成 JUnit
使用示例(JUnit 5)
java
package com.hspedu.junit_;
import org.junit.jupiter.api.Test;
/**
* @author 韩顺平
* @version 1.0
*/
public class JUnit_ {
// 传统方式测试
public static void main(String[] args) {
new JUnit_().m1();
new JUnit_().m2();
}
@Test
public void m1() {
System.out.println("m1 方法被调用");
}
@Test
public void m2() {
System.out.println("m2 方法被调用");
}
@Test
public void m3() {
System.out.println("m3 方法被调用");
}
}